/*
============================================================================
 Name        : nmsJpeg.c
 Author      : Jian
 Version     :
 Copyright   : base on http://ce.sharif.edu/~ahmadinejad/jpeg/
 Description : nmsJpeg.c - source file
============================================================================
*/

/* INCLUDE FILES */
#include "nmsJpeg.h"
#include "nmsHuffman.h"
#include "nmsFrame.h"
#include "nmsJpegUtil.h"
#include "nmsJpegDct.h"


#define __SEP   0xFF    /* Maker Separator */
#define __APP0  0xE0    /* JPEG FILE indictor */
#define __SOI   0xD8    /* Start of image */
#define __EOI   0xD9    /* End of image */
#define __DQT   0xDB    /* Define Quantization table */
#define __DHT   0xC4    /* Define Huffman table */
#define __SOF0  0xC0    /* Start of frame 0, only support SOF0, for SOF1 - SOF7 not support */
#define __SOS   0xDA    /* Start of scan ... till end or got the __EOI */

#define __APP0_LEN  16

extern const UINT8 _zigZagScanner[64] ;

static int _readAPP0(nmsJpeg* jpg)
{
    int len      = 0 ;
    char iden[5] = {0} ;

    /* len */
    if ((len = nmsJpegRead2ByteBigEndian(jpg->fileHandle)) != __APP0_LEN) {
        nprintf("error: read APP0 len != __APP0_LEN") ;
        return -1 ;
    }

    /* identify */
    nmsJpegReadFile(jpg->fileHandle, iden, 5) ;
    if (nmsStrCaseCmp(iden, "JFIF") != 0) {
        nprintf("error: read APP0 identify = %s", iden) ;
        return -1 ;
    }

    nmsFsSeek(jpg->fileHandle, __APP0_LEN - (2 + 5), NMS_FS_FILE_CURRENT) ;
    return 0 ;
}

static int _readDQT(nmsJpeg* jpg)
{
    int len      = 0 ;
    int index    = 0 ;
    int qtUnit   = 0 ;
    UINT8 byte   = -1 ;
    UINT16 byte2 = -1 ;
    int i        = 2 ;

    len = nmsJpegRead2ByteBigEndian(jpg->fileHandle) ;

    /* NOTE: one DQT maker may contain more than 1 table */
    while(i < len ) {

        int j = 0 ;

        nmsJpegReadFile(jpg->fileHandle, &byte, 1) ;

        if ((index = (byte & 0x0F)) > __NMS_MAX_DQT_INDEX) {
            nprintf("error: read DQT index = %d", index) ;
            return -1 ;
        }

        jpg->qt[index] = (nmsJpegDqt*)nmsJpegMalloc(sizeof(nmsJpegDqt)) ;
        nmsMemSet(jpg->qt[index], 0, sizeof(nmsJpegDqt)) ;

        qtUnit = ((byte >> 4) == 0) ? 1 : 2 ;

        while(j < 64) {
            nmsJpegReadFile(jpg->fileHandle, &byte2, qtUnit) ;
            if (qtUnit == 1)
                (*jpg->qt[index])[_zigZagScanner[j]] = (byte2 & 0xFF) ;
            else
                (*jpg->qt[index])[_zigZagScanner[j]] = __COVERT_2BYTE_ENDIAN(byte2) ;
            j++ ;
        }

        i += (1 + qtUnit * 64) ;
    }

    return 0 ;
}

static __NMS_INLINE int _readDHT(nmsJpeg* jpg)
{
    return nmsJpegHuffmanReadFrom(jpg->fileHandle, jpg->acTable, jpg->dcTable) ;
}

static __NMS_INLINE int _readSOE0(nmsJpeg* jpg)
{
    jpg->frame = nmsJpegFrameReadFrom(jpg->fileHandle) ;
    return jpg->frame ? 0 : -1 ;
}

static int _readSOS(nmsJpeg* jpg)
{
    UINT8 byte      = 0 ;
    int type        = 0 ;
    int i           = 0 ;
    nmsSosComp comp = { 0 };

    int len = nmsJpegRead2ByteBigEndian(jpg->fileHandle) ;

    nmsJpegReadFile(jpg->fileHandle, &type, 1) ;
    if (type != __NMS_JPEG_GREY && type != __NMS_JPEG_YUV) {   /* only support gray and YUV */
        nprintf("error: read SOC type = %d not support", type) ;
        goto _error ;
    }

    for (; i < type; i++) {
        nmsJpegReadFile(jpg->fileHandle, &comp.id, 1) ;

        if (comp.id <= 0 || comp.id > type || comp.id != (i + 1)) {
            nprintf("error: read SOS comp id = %d not correct", byte) ;
            goto _error ;
        }

        nmsJpegReadFile(jpg->fileHandle, &byte, 1) ;
        comp.acHTIndex = byte >> 4 ;
        comp.dcHTIndex = byte & 0x0F ;

        if (comp.acHTIndex > __NMS_MAX_DHT_INDEX || comp.dcHTIndex > __NMS_MAX_DHT_INDEX) {
            nprintf("error: read SOS comp acHTIndex = %d, dcHTIndex = %d, not correct", comp.acHTIndex, comp.dcHTIndex) ;
            goto _error ;
        }

        jpg->sosComp[comp.id] = comp ;
    }

    /* Just ignore last 3 byte to real image data */
    nmsFsSeek(jpg->fileHandle, 3, NMS_FS_FILE_CURRENT) ;

    return 0 ;

_error:
    return -1 ;
}

static int _readJpg(nmsJpeg* jpg)
{
    UINT8  byte  = 0 ;
    UINT16 byte2 = 0 ;
    int    i     = 0 ;
    int    hsMax = 0 ;
    int    vsMax = 0 ;

    jpg->imgDataPos = -1 ;

    nmsFsSeek(jpg->fileHandle, 0, NMS_FS_FILE_BEGIN) ;

    nmsJpegReadFile(jpg->fileHandle, &byte2, 2) ;
    if ((byte2 & 0xFF) != __SEP || (byte2 >> 8) != __SOI) {
        nprintf("error: the file don't have the SOI maker") ;
        return -1 ;
    }

    while(1) {

        if (nmsJpegReadFile(jpg->fileHandle, &byte, 1) < 0) {
            nprintf("read data error or the EOI maker is missing") ;
            return -1 ;
        }

        if (byte != __SEP)  continue ;

        byte = 0 ;

        do {
            nmsJpegReadFile(jpg->fileHandle, &byte, 1) ;
        } while (byte == __SEP);

        if (byte == __EOI) {
            nprintf("error : got the end the file, we should never go to here") ;
            break;
        } else if (byte == __APP0) {
            if (_readAPP0(jpg) < 0) {
                nprintf("error: read APP0 maker") ;
                return -1 ;
            }
        } else if (byte == __DQT) {
            if (_readDQT(jpg) < 0) {
                nprintf("error: read DQT maker") ;
                return -1 ;
            }
        } else if (byte == __DHT) {
            if (_readDHT(jpg) < 0) {
                nprintf("error: read DHT maker") ;
                return -1 ;
            }
        } else if (byte == __SOF0) {
            if (_readSOE0(jpg) < 0) {
                nprintf("error: read SOF0 maker") ;
                return -1 ;
            }
        } else if (byte > __SOF0 && byte <= 0xCF) {
            nprintf("error: unsupported frame maker = %d", byte) ;
            return -1 ;
        } else if (byte == __SOS) {
            if (_readSOS(jpg) < 0) {
                nprintf("error: read SOS maker") ;
                return -1 ;
            }

            jpg->imgDataPos = nmsFsTell(jpg->fileHandle) ;
            nprintf("SUCCEED: got the sos maker, we can do the decompress from %d now", jpg->imgDataPos) ;
            break;
        } else {
            int mLen = 0 ;
            nprintf("_readJpg got unsupport maker = %d, jump over it", byte) ;
            nmsJpegReadFile(jpg->fileHandle, &byte2, 2) ;
            mLen = __COVERT_2BYTE_ENDIAN(byte2) ;
            nmsFsSeek(jpg->fileHandle, mLen - 2, NMS_FS_FILE_CURRENT) ;
        }
    }

    __NMS_ASSERT_DEFENSE(jpg->imgDataPos != -1, return -1) ;

    /* finally we do the validation */
    if (!jpg->frame)  return -1 ;

    for (i = __NMC_JPEG_COMP_ID_Y; i <= __NMC_JPEG_COMP_ID_CR; i++) {
        if (jpg->frame->comp[i].id != __NMC_JPEG_COMP_ID_NONE && jpg->qt[jpg->frame->comp[i].qtIndex] == NULL) {
            nprintf("error: validation qt = %d is not exist", jpg->frame->comp[i].qtIndex) ;
            return -1 ;
        }

        if (jpg->sosComp[i].id != __NMC_JPEG_COMP_ID_NONE
            && (jpg->acTable[jpg->sosComp[i].acHTIndex] == NULL || jpg->dcTable[jpg->sosComp[i].dcHTIndex] == NULL)) {
            nprintf("error: validation acHTIndex = %d or dcHTIndex = %d not exist", jpg->sosComp[i].acHTIndex, jpg->sosComp[i].dcHTIndex) ;
            return -1 ;
        }

        if (hsMax < jpg->frame->comp[i].hSample)
            hsMax = jpg->frame->comp[i].hSample ;

        if (vsMax < jpg->frame->comp[i].vSample)
            vsMax = jpg->frame->comp[i].vSample ;
    }

    if (hsMax * vsMax > 4) {
        nprintf("read jpeg error: unsupport hSample = %d, vSample = %d", hsMax, vsMax) ;
        return -1 ;
    }

    jpg->mcuWidth   = hsMax * 8 ;
    jpg->mcuHeight  = vsMax * 8 ;

    jpg->mcuPerLine = jpg->frame->width / jpg->mcuWidth + (((jpg->frame->width % jpg->mcuWidth) == 0) ?  0 : 1);
    jpg->mcuLine    = jpg->frame->height / jpg->mcuHeight + (((jpg->frame->height % jpg->mcuHeight) == 0) ? 0 : 1);

    nprintf("read jpeg succeed width = %d, height = %d, hSapmleMax = %d, vSapmleMax = %d", jpg->frame->width, jpg->frame->height, hsMax, vsMax) ;

    return 0 ;
}

static __NMS_INLINE int _copyTo(nmsJpeg* jpg, const WCHAR* name)
{
    int ret    = 0 ;
    int curPos = nmsFsTell(jpg->fileHandle) ;

    nmsFsClose(jpg->fileHandle) ;
    ret = nmsFsCopyW(jpg->fileName, (WCHAR*)name) ;

    jpg->fileHandle = nmsFsOpenW(jpg->fileName, NMS_FS_READ) ;
    nmsFsSeek(jpg->fileHandle, curPos, NMS_FS_FILE_BEGIN) ;

    return ret ;
}

nmsJpeg* nmsJpegOpen(const WCHAR* aFilename)
{
    nmsJpeg* jpg = NULL ;

    __NMS_ASSERT_DEFENSE(aFilename, return NULL) ;

    jpg = (nmsJpeg*)nmsJpegMalloc(sizeof(nmsJpeg)) ;

    if (!jpg) {
        nprintf("error: malloc memory for jpeg") ;
        goto _error ;
    }

    nmsMemSet(jpg, 0, sizeof(nmsJpeg)) ;

    jpg->fileHandle = nmsFsOpenW((WCHAR*)aFilename, NMS_FS_READ) ;
    if (jpg->fileHandle == 0) {
        nprintf("error: open file") ;
        goto _error ;
    }

    nmsFsGetFileSizeWithNameW((WCHAR*)aFilename, &(jpg->fileSize)) ;
    if (jpg->fileSize <= 50) {
        nprintf("error: file size = %d is too small", jpg->fileSize) ;
        goto _error ;
    }

    if (_readJpg(jpg) < 0) {
        nprintf("error : read jpg") ;
        goto _error ;
    }

    jpg->fileName = nmsJpegMalloc((nmsUcs2Strlen((WCHAR*)aFilename) + 1) * 2) ;
    nmsUcs2Cpy((void*)jpg->fileName, (void*)aFilename) ;

    return jpg ;

_error:

    if (jpg)  nmsJpegClose(jpg) ;

    return NULL ;
}

void nmsJpegClose(nmsJpeg* jpg)
{
    int i = 0 ;

    __NMS_ASSERT_DEFENSE(jpg, return) ;

    if (jpg->fileName)         nmsJpegFree(jpg->fileName) ;
    if (jpg->fileHandle != 0)  nmsFsClose(jpg->fileHandle) ;
    if (jpg->frame)            nmsJpegFrameFree(jpg->frame) ;

    for(i = 0; i <= __NMS_MAX_DQT_INDEX; i++) {
        if (jpg->qt[i])        nmsJpegFree(jpg->qt[i]) ;
    }

    for (i = 0; i <= __NMS_MAX_DHT_INDEX; i++) {
        if (jpg->acTable[i])   nmsJpegHuffmanFree(jpg->acTable[i]) ;
        if (jpg->dcTable[i])   nmsJpegHuffmanFree(jpg->dcTable[i]) ;
    }

    nmsJpegFree(jpg) ;
}

int nmsJpegGetSize(const nmsJpeg* jpg, int* width, int* height)
{
    __NMS_ASSERT_DEFENSE(jpg && jpg->frame && width && height, return -1) ;

    *width  = jpg->frame->width ;
    *height = jpg->frame->height ;
    return 0 ;
}

int nmsJpegSave(nmsJpeg* jpg, const WCHAR* aFilename, UINT32 scale)
{
    int dumpHandle = 0 ;
    int ret        = -1 ;

    __NMS_ASSERT_DEFENSE(jpg && aFilename && (scale > 0 && scale <= 100) , return -1) ;

    if (nmsUcs2StrCaseCmp(jpg->fileName, (WCHAR*)aFilename) == 0) {
        nprintf("nmsJpegSave error: same file name") ;
        return -1 ;
    }

#ifndef __NMS_JPEG_DEBUG
    if (scale == 100 || jpg->fileSize < (5 * 1024) || (jpg->frame->width * jpg->frame->height) < (32 * 32)) {
        nprintf("nmsJpegSave just copy file") ;
        ret = _copyTo(jpg, aFilename) ;
        goto _exit ;
    }
#endif

    if ((dumpHandle = nmsFsOpenW((WCHAR*)aFilename, NMS_FS_CREATE_ALWAYS)) == 0) {
        nprintf("error: open save file error") ;
        goto _exit ;
    }

    ret = nmsJpegCompress(jpg, dumpHandle, scale) ;

_exit:

    if (dumpHandle != 0)  nmsFsClose(dumpHandle) ;

    return ret ;
}

/* End of File */
